/*
 * TransiCast / Qt
 * (c) 2012 Joachim Pfeiffer
 * Please refer to http://code.google.com/p/transitacst for more information
 */
#include <string>
#include <stdio.h>
#include <QString>
#include <QTimer>
#include <QAbstractItemView>
#include <QBrush>
#include <QPixmap>
#include <QApplication>
#include <QMainWindow>
#include <QFile>
#include <QTextStream>

#include <QItemSelectionModel>
#include <QModelIndexList>
#include <QModelIndex>

#include "transitreemodel.hpp"
#include "busynotify.hpp"
#include "tsandcs.hpp"
#include "messagebox.hpp"

#include <QNetworkConfigurationManager>

TransiTreeModel::TransiTreeModel(QMainWindow *parent, QTreeView *parentView, QApplication *parentApp, BusyNotify *parentHourglass, const QString version) :
    QStandardItemModel(parent)
{
    QStandardItemModel::clear();
    modelParent = parent;
    view = parentView;
    app = parentApp;
    hourglass = parentHourglass;
    parentVersion = version;

    utilities = new Utilities();
    transitDataServer = "http://transicast.appspot.com/";
    servletPath = "ws/V1/";
    agencyQuery = "agencies";
    routeListQuery = "routes?agency=";
    routeAgencyQuery = "termini?agency=";
    departureQuery = "arrivals?locIDs=";
    detourQuery = "detours?locId=";
    routeQuery = "route=";
    departureRouteQuery = "routes=";
    detourRouteQuery = "routes=";
    departureAgencyQuery = "agency=";
    appId = "appid=sample522are9";
    relayQuery = "relay=";
    versionQuery = "ver=";
    personality = "_010";

    location = NULL;
    isUpdatingLocation = false;
    isSingleShot = true;
    closestStopName = "";
    locationTimedOut = false;

    curRequestAgencies = false;
    curRequestAgency = NULL;
    curRequestRoute = NULL;
    curRequestStop = NULL;
    curRequestStopDetour = NULL;

    slotRequestAgency = NULL;
    slotRequestRoute = NULL;
    trackingRoute = NULL;
    slotRequestStop = NULL;

    slotClicked = false;
    departureRequestTimer = NULL;

    agencyReader = NULL;
    routeListReader = NULL;
    routeReader = NULL;
    departureReader = NULL;
    detourReader = NULL;

    foreground = new QBrush();

    versionChecked = false;
    checkingVersion = false;

    bookmarkAgency = NULL;
    bookmarkFollowupRoute = "";
    bookmarkFollowupStop = "";

    departureQueryCtr = 0;

    displayMsg("Initializing");

    app->processEvents();

    QTimer::singleShot(0, this, SLOT(delayedInit()));
}

void TransiTreeModel::delayedInit()
{
    QFile file("transicast.conf");
    if (!file.exists()) {
        TsAndCs dialog(modelParent, app, parentVersion);
        dialog.exec();
        dialog.deleteLater();
    }

    QNetworkConfigurationManager manager;
    const bool canStartIAP = (manager.capabilities() & QNetworkConfigurationManager::CanStartAndStopInterfaces);
    QNetworkConfiguration cfg = manager.defaultConfiguration();
    if (!cfg.isValid() || (!canStartIAP && cfg.state() != QNetworkConfiguration::Active)) {
        displayMsg("No available access points found");
        return;
    }
    displayMsg("Connecting to Network");
    m_session = new QNetworkSession(cfg, this);
    connect(m_session, SIGNAL(opened()), this, SLOT(startNonGpsSingleShot()));
    m_session->open();

    QPixmap pixmap(":/TCpromo.PNG");
    splash = new QSplashScreen(pixmap, Qt::WindowStaysOnTopHint);
    splash->show();

    QTimer::singleShot(0, this, SLOT(killSwitch()));
}

void TransiTreeModel::finishSplash()
{
    if (!splash)
        return;
    splash->finish(modelParent);
    delete splash;
    splash = NULL;
}

/*
 * Version Check
 */
void TransiTreeModel::killSwitch()
{
    if (checkingVersion || versionChecked)
        return;
    checkingVersion = true;
    killSwitchNetworkAccess = new QNetworkAccessManager(this);
    connect(killSwitchNetworkAccess, SIGNAL(finished(QNetworkReply *)), this, SLOT(readKillSwitch(QNetworkReply *)));
    requestKillSwitch();
}
void TransiTreeModel::requestKillSwitch()
{
    QString url = "http://www.transicast.com/killQt" + parentVersion + ".html";
    killSwitchNetworkAccess->get(QNetworkRequest(QUrl(url)));
}
void TransiTreeModel::readKillSwitch(QNetworkReply *reply)
{
      if (reply->error() != QNetworkReply::NoError) {
        versionChecked = true;
        checkingVersion = false;
        reply->deleteLater();
        killSwitchNetworkAccess->deleteLater();
        return;
    }
    messageBox = new QMessageBox(modelParent);
    messageBox->setText("\nTransiCast " + parentVersion + "\n\nSupport for version " + parentVersion + " has ended\n\nPlease visit www.transicast.com for update information\n");
    messageBox->setIcon(QMessageBox::Critical);
    messageBox->setAttribute(Qt::WA_TranslucentBackground, false);
    messageBox->addButton(QMessageBox::Ok);
    messageBox->exec();
    messageBox->deleteLater();
    app->exit(0);
}

/*
 * Load Agencies
 */
void TransiTreeModel::loadAgencies()
{
    // Escape if already loading
    if (curRequestAgencies && m_session->isOpen()) {
        if (agencyReader) {
            agencyReader->clear();
            delete agencyReader;
            agencyReader = NULL;
        }
    }

    if (updatingLocation())
        return;

    curRequestAgencies = true;

    if (!locationTimedOut && (location == NULL || !yougeoPosInfo.isValid()))
        startLocation(QGeoPositionInfoSource::NonSatellitePositioningMethods, true, NULL);

    agencyNetworkXmlAccess = new QNetworkAccessManager(this);
    connect(agencyNetworkXmlAccess, SIGNAL(finished(QNetworkReply *)), this, SLOT(readAgencies(QNetworkReply *)));
    requestAgencies();
    hourglass->showBusyCursor();
}

QStandardItem *TransiTreeModel::getRequestAgency()
{
    return slotRequestAgency;
}

void TransiTreeModel::requestAgencies()
{
    finishSplash();
    displayMsg("Requesting Agencies");
    std::string relayBuf;
    relayBuf = utilities->createAppKey();
    QString url = transitDataServer + servletPath + agencyQuery + "?";
    if (yougeoPosInfo.isValid()) {
        char latQuery[80];
        char lonQuery[80];
        QString latQueryS;
        QString lonQueryS;
        sprintf(latQuery,"lat=%f&", yougeoPosInfo.coordinate().latitude());
        sprintf(lonQuery,"lon=%f&", yougeoPosInfo.coordinate().longitude());
        latQueryS = latQuery;
        lonQueryS = lonQuery;
        url += latQueryS + lonQueryS;
    }
    url += appId + "&" + relayQuery + (QString)relayBuf.c_str() + "&" + versionQuery + parentVersion + personality;
    agencyNetworkXmlAccess->get(QNetworkRequest(QUrl(url)));
}

void TransiTreeModel::nextAgency()
{
    QXmlStreamReader::TokenType token;
    QXmlStreamAttributes attributes;
    int rowCount;
    QString commonName;
    QString name;

    if (!agencyReader)
        return;

    if (updatingLocation()) {
        clearDepartureRequestTimer();
        clearDepartures();
        return;
    }

    if (!agencyReader->atEnd() && !agencyReader->hasError()) {

        try {
            token = agencyReader->readNext();
            if (token == QXmlStreamReader::StartElement) {
                attributes = agencyReader->attributes();
                if (attributes.hasAttribute("id") && attributes.hasAttribute("rt")) {

                    // Add agency details
                    rowCount = this->rowCount();
                    commonName = attributes.value("", "common").toString();
                    name = attributes.value("", "name").toString();
                    if (!commonName.isEmpty())
                        name += " (" + commonName + ")";
                    this->appendRow(new QStandardItem(name));
                    this->item(rowCount, COL_NAME)->setEditable(false);
                    this->setItem(rowCount, COL_ID, new QStandardItem(attributes.value("", "id").toString()));
                    agencyTops.append(new QStandardItem(attributes.value("", "top").toString()));
                    agencyLefts.append(new QStandardItem(attributes.value("", "left").toString()));
                    agencyBottoms.append(new QStandardItem(attributes.value("", "bottom").toString()));
                    agencyRights.append(new QStandardItem(attributes.value("", "right").toString()));
                    this->setItemDistance(rowCount);
                    agencyRts.append(new QStandardItem(attributes.value("", "rt").toString()));

                    // Hide support columns
                    if (rowCount == 0)
                        for (int i = COL_NAME + 1; i <= COL_DIST_KM; ++i)
                            view->setColumnHidden(i, true);

                    contextSort();
                }
            }

            QTimer::singleShot(0, this, SLOT(nextAgency()));
        } catch (...) {
            agencyNetworkReply->deleteLater();
            agencyReader->clear();
            delete agencyReader;
            agencyReader = NULL;
            agencyNetworkXmlAccess->deleteLater();
            displayMsg("Error reading agencies");
            curRequestAgencies = false;
        }
    } else {
        agencyNetworkReply->deleteLater();
        agencyReader->clear();
        delete agencyReader;
        agencyReader = NULL;
        agencyNetworkXmlAccess->deleteLater();
        QTimer::singleShot(0, this, SLOT(refreshDistances()));
        displayMsg("Done");
        curRequestAgencies = false;
    }
}

void TransiTreeModel::readAgencies(QNetworkReply *reply)
{
    hourglass->hideBusyCursor();

    if (this->hasChildren()) {
        reply->deleteLater();
        return;
    }

    displayMsg("Loading Agencies");

    if (reply->error() != QNetworkReply::NoError) {
        displayMsg("Server or Network Error");
        reply->deleteLater();
        connect(m_session, SIGNAL(opened()), this, SLOT(startNonGpsSingleShot()));
        resetSession();
        return;
    }

    agencyNetworkReply = reply;
    agencyReader = new QXmlStreamReader(reply);

    QTimer::singleShot(0, this, SLOT(nextAgency()));
}

/*
 * Load Route List
 */
void TransiTreeModel::loadRoutes(QStandardItem *agency)
{
    if (agency->rowCount() > 0 || view->isExpanded(this->indexFromItem(agency))) {
        if (view->isExpanded(this->indexFromItem(agency))) {
            view->collapse(this->indexFromItem(agency));
            manageSiblingVisibility(agency, false);
            view->scrollTo(this->indexFromItem(agency), QAbstractItemView::PositionAtTop);
        } else {
            view->expand(this->indexFromItem(agency));
            manageSiblingVisibility(agency, true);
        }
        return;
    }

    // Escape if already loading
    if (curRequestAgency && m_session->isOpen())
        return;
    curRequestAgency = agency;

    // Escape if no network session
    if (!m_session->isOpen()) {
        resetSession();
        connect(m_session, SIGNAL(opened()), this, SLOT(loadRoutes()));
        return;
    }

    // Stop agency load
    if (agencyReader) {
        agencyReader->clear();
        delete agencyReader;
        agencyReader = NULL;
    }

    routeListNetworkXmlAccess = new QNetworkAccessManager(this);
    connect(routeListNetworkXmlAccess, SIGNAL(finished(QNetworkReply *)), this, SLOT(readRoutes(QNetworkReply *)));
    requestRoutes(agency);
    hourglass->showBusyCursor();
}
void TransiTreeModel::loadRoutes()
{
    QStandardItem *requestAgencyHelper = curRequestAgency;
    curRequestAgency = NULL;
    loadRoutes(requestAgencyHelper);
}

QStandardItem *TransiTreeModel::getRequestRoute()
{
    return slotRequestRoute;
}
QStandardItem *TransiTreeModel::getSelectedRoute()
{
    QModelIndexList selection = view->selectionModel()->selectedIndexes();
    if (selection.count() < 1)
        return NULL;
    QModelIndex first = selection.first();
    QStandardItem *item = this->itemFromIndex(first);
    if (!item->parent()) // Agency selected
        return NULL;
    if (item->parent()->parent())
        if (item->parent()->parent()->parent()) // Departure detail selected
            return item->parent()->parent();
        else
            return item->parent();
    return item; // Route selected
}

void TransiTreeModel::requestRoutes(QStandardItem *agency)
{
    QString agencyName = agency->text();
    displayMsg("Requesting Routes");
    std::string relayBuf;
    relayBuf = utilities->createAppKey();
    QString url = transitDataServer + servletPath + routeListQuery + agencyName + "&" + appId + "&" + relayQuery + (QString)relayBuf.c_str() + "&" + versionQuery + parentVersion + personality;
    routeListNetworkXmlAccess->get(QNetworkRequest(QUrl(url)));
}

void TransiTreeModel::nextRoute()
{
    QXmlStreamReader::TokenType token;
    QXmlStreamAttributes attributes;

    if (!routeListReader->atEnd() && !routeListReader->hasError()) {
        try {
            token = routeListReader->readNext();
            if (token == QXmlStreamReader::StartElement) {
                attributes = routeListReader->attributes();
                if (attributes.hasAttribute("id")) {

                    // Add route list item
                    QStandardItem *route = new QStandardItem(attributes.value("", "id").toString());
                    route->setEditable(false);
                    curRequestAgency->appendRow(route);
                }
            }
            QTimer::singleShot(0, this, SLOT(nextRoute()));
        } catch (...) {
            displayMsg("Error reading routes");
            routeListNetworkReply->deleteLater();
            routeListReader->clear();
            routeListNetworkXmlAccess->deleteLater();
            delete routeListReader;
            routeListReader = NULL;
        }
    } else {
        displayMsg("Done");
        routeListNetworkReply->deleteLater();
        routeListReader->clear();
        routeListNetworkXmlAccess->deleteLater();
        delete routeListReader;
        routeListReader = NULL;
        view->expand(this->indexFromItem(curRequestAgency));
        manageSiblingVisibility(curRequestAgency, true);
        view->scrollTo(this->indexFromItem(curRequestAgency), QAbstractItemView::PositionAtTop);
        curRequestAgency = NULL;

        // If route list loaded from bookmark selection: roll out bookmark details
        if (bookmarkFollowupRoute.length() > 0)
            if (bookmarkAgency != NULL)
                QTimer::singleShot(0, this, SLOT(bookmarkFollowup()));
    }
}

void TransiTreeModel::readRoutes(QNetworkReply *reply)
{
    displayMsg("Loading Routes");

    hourglass->hideBusyCursor();

    if (reply->error() != QNetworkReply::NoError) {
        displayMsg("Server or Network Error");
        reply->deleteLater();
        if (routeListReader)
            routeListReader->clear();
        resetSession();
        curRequestAgency = NULL;
        connect(m_session, SIGNAL(opened()), this, SLOT(loadRoutes()));
        return;
    }

    routeListNetworkReply = reply;
    routeListReader = new QXmlStreamReader(reply);

    QTimer::singleShot(0, this, SLOT(nextRoute()));
}

/*
 * Load Route
 */
void TransiTreeModel::loadRoute(QStandardItem *route)
{
    // Collapse open route
    for (int j = 0; j < route->parent()->rowCount(); ++j)
        if (route != route->parent()->child(j, COL_NAME))
            view->collapse(this->indexFromItem(route->parent()->child(j, COL_NAME)));

    // If selected route already loaded: expand/collapse
    if (route->rowCount() > 0) {
        if (view->isExpanded(this->indexFromItem(route)))
            view->collapse(this->indexFromItem(route));
        else
            view->expand(this->indexFromItem(route));
        return;
    }

    // Reload if already loading
    if (routeReader) {
        routeReader->clear();
        delete routeReader;
        routeReader = NULL;
    }

    // Escape if already loading
    curRequestRoute = route;

    // Escape if no network session
    if (!m_session->isOpen()) {
        resetSession();
        connect(m_session, SIGNAL(opened()), this, SLOT(loadRoute()));
        return;
    }

    routeNetworkXmlAccess = new QNetworkAccessManager(this);
    connect(routeNetworkXmlAccess, SIGNAL(finished(QNetworkReply *)), this, SLOT(readRoute(QNetworkReply *)));
    requestRoute(route);
    hourglass->showBusyCursor();
}
void TransiTreeModel::loadRoute()
{
    QStandardItem *requestRouteHelper = curRequestRoute;
    curRequestRoute = NULL;
    loadRoute(requestRouteHelper);
}

void TransiTreeModel::requestRoute(QStandardItem *route)
{
    QString routeName = route->text();
    displayMsg("Requesting Route");
    std::string relayBuf;
    relayBuf = utilities->createAppKey();
    QString agencyName = route->parent()->text();
    QString url = transitDataServer + servletPath + routeAgencyQuery + agencyName + "&" + routeQuery + routeName + "&all=full" + "&" + appId + "&" + relayQuery + (QString)relayBuf.c_str() + "&" + versionQuery + parentVersion + personality;
    routeNetworkXmlAccess->get(QNetworkRequest(QUrl(url)));
    slotRequestRoute = route;
}

void TransiTreeModel::nextStop()
{
    QXmlStreamReader::TokenType token;
    QXmlStreamAttributes attributes;
    QStandardItem *stop, *id, *pre, *lat, *lon;
    QBrush *foreground = new QBrush();
    foreground->setColor(Qt::yellow);

    if (routeReader == NULL || curRequestRoute == NULL)
        return;

    if (!routeReader->atEnd() && !routeReader->hasError()) {
        try {
            token = routeReader->readNext();
            if (token == QXmlStreamReader::StartElement) {
                attributes = routeReader->attributes();
                if (attributes.hasAttribute("desc")) {

                    // Add route detail: stop. Insert in sequence based on whether stop is named as predecessor
                    QList<QStandardItem *> row;
                    stop = new QStandardItem(attributes.value("", "desc").toString());
                    stop->setEditable(false);
                    row << stop;
                    id = new QStandardItem(attributes.value("", "locid").toString());
                    row << id;
                    pre = new QStandardItem(attributes.value("", "pre").toString());
                    row << pre;
                    lat = new QStandardItem(attributes.value("", "lat").toString());
                    row << lat;
                    lon = new QStandardItem(attributes.value("", "lng").toString());
                    row << lon;
                    if (attributes.hasAttribute("route"))
                        stop->setForeground(*foreground);
                    curRequestRoute->appendRow(row);
                }
            }
            QTimer::singleShot(0, this, SLOT(nextStop()));
        } catch (...) {
            displayMsg("Error reading route");
            routeNetworkReply->deleteLater();
            routeNetworkXmlAccess->deleteLater();
            routeReader->clear();
            delete routeReader;
            routeReader = NULL;
        }
    } else {
        displayMsg("Done");
        view->scrollTo(this->indexFromItem(curRequestRoute), QAbstractItemView::PositionAtTop);
        routeNetworkReply->deleteLater();
        routeNetworkXmlAccess->deleteLater();
        routeReader->clear();
        delete routeReader;
        routeReader = NULL;
        view->expand(this->indexFromItem(curRequestRoute));

        // If route list loaded from bookmark selection: roll out bookmark details
        if (bookmarkFollowupStop.length() > 0) {
            bookmarkFollowupDepartureRoute = curRequestRoute;
            QTimer::singleShot(0, this, SLOT(bookmarkFollowupDeparture()));
        }

        curRequestRoute = NULL;
    }
}

void TransiTreeModel::readRoute(QNetworkReply *reply)
{
    displayMsg("Loading Route");

    hourglass->hideBusyCursor();

    if (reply->error() != QNetworkReply::NoError){
        displayMsg("Server or Network Error");
        reply->deleteLater();
        if (routeReader)
            routeReader->clear();
        resetSession();
        curRequestRoute = NULL;
        connect(m_session, SIGNAL(opened()), this, SLOT(loadRoute()));
        return;
    }

    routeNetworkReply = reply;
    routeReader = new QXmlStreamReader(reply);

    QTimer::singleShot(0, this, SLOT(nextStop()));
}

/*
 * Load Departures
 */
void TransiTreeModel::loadDepartures(QStandardItem *stop, bool clicked)
{
    // Reload if already loading
    if (departureReader) {
        departureReader->clear();
        delete departureReader;
        departureReader = NULL;
    }

    if (clicked && slotRequestStop != NULL && slotRequestStop != stop) {
        slotRequestStop->removeRows(0, slotRequestStop->rowCount());
        curRequestStop = NULL; // clearConnections(); //
    }

    if (stop->rowCount() > 0) {
        if (clicked) {
            if (view->isExpanded(this->indexFromItem(stop))) {
                view->collapse(this->indexFromItem(stop));
                stop->removeRows(0, stop->rowCount());
            } else
                view->expand(this->indexFromItem(stop));
            curRequestStop = NULL; // clearConnections(); //
            return;
        } else {

            // Remove existing departure rows
            QStandardItem *iterDestination;
            for (int i = 0; i < stop->rowCount(); ++i) {
                iterDestination = stop->child(i, COL_NAME);
                iterDestination->removeRows(0, iterDestination->rowCount());
            }

            // Pause after 15mins
            if (departureQueryCtr > 30) {
                messageBox = new QMessageBox(modelParent);
                messageBox->setModal(true);
                messageBox->setText("Departure queries paused");
                QPixmap appIconPixmap(":/appico.png");
                messageBox->setIconPixmap(appIconPixmap);
                messageBox->setAttribute(Qt::WA_TranslucentBackground, false);
                messageBox->addButton(QMessageBox::Ok);
                messageBox->exec();
                departureQueryCtr = 0;
                messageBox->deleteLater();
            }
        }
    }

    if (!m_session->isOpen()) {
        displayMsg("Network Connection Lost");
        if (departureRequestTimer)
            delete departureRequestTimer;
        departureRequestTimer = new QTimer(this);
        connect(departureRequestTimer, SIGNAL(timeout()), this, SLOT(loadDepartures()));
        departureRequestTimer->setSingleShot(true);
        departureRequestTimer->start(30000);
        resetSession();
        connect(m_session, SIGNAL(opened()), this, SLOT(loadDepartures()));
        curRequestStop = NULL; // clearConnections(); //
        return;
    }

    curRequestStop = stop;
    departuresNetworkXmlAccess = new QNetworkAccessManager(this);
    connect(departuresNetworkXmlAccess, SIGNAL(finished(QNetworkReply *)), this, SLOT(readDepartures(QNetworkReply *)));
    requestDepartures(stop);
    hourglass->showBusyCursor();
}

void TransiTreeModel::loadDepartures()
{
    if (slotRequestStop != NULL)
        loadDepartures(slotRequestStop, slotClicked);
}
void TransiTreeModel::clearDepartureRequestTimer()
{
    if (departureRequestTimer)
        departureRequestTimer->stop();

    departureQueryCtr = 0;
}
void TransiTreeModel::clearDepartures()
{
    if (slotRequestStop)
        slotRequestStop->removeRows(0, slotRequestStop->rowCount());
    slotRequestStop = NULL;
    slotClicked = false;
    hourglass->hideBusyCursor();

    departureQueryCtr = 0;
}
QStandardItem *TransiTreeModel::getRequestStop()
{
    return slotRequestStop;
}

void TransiTreeModel::requestDepartures(QStandardItem *stop)
{
    QStandardItem *route = stop->parent();
    QStandardItem *agency = route->parent();
    QString stopName = route->child(stop->row(), COL_NAME)->text();
    QString routeNames = "";
    QString stopIds = "";
    QStandardItem *iterStop;
    for (int i = 0; i < route->rowCount(); ++i) {
        iterStop = route->child(i, COL_NAME);
        if (iterStop->text().compare(stopName) == 0) {
            iterStop = route->child(i, COL_ID);
            if (stopIds.length() == 0) {
                stopIds = iterStop->text();
                routeNames = route->text();
            } else {
                stopIds += "," + iterStop->text();
                routeNames += "," + route->text();
            }
        }
    }

    QString agencyName = agency->text();
    displayMsg("Requesting Departures");
    std::string relayBuf;
    relayBuf = utilities->createAppKey();
    QString url = transitDataServer + servletPath + departureQuery + stopIds + "&" + departureRouteQuery + routeNames + "&" + departureAgencyQuery + agencyName+ "&" + appId + "&" + relayQuery + (QString)relayBuf.c_str() + "&" + versionQuery + parentVersion + personality;
    departuresNetworkXmlAccess->get(QNetworkRequest(QUrl(url)));
    slotRequestRoute = route;
    slotRequestAgency = agency;
}

void TransiTreeModel::nextDeparture()
{
    QXmlStreamReader::TokenType token;
    QXmlStreamAttributes attributes;
    QStandardItem *destination;

    if (departureReader == NULL || curRequestStop == NULL)
        return;

    if (!departureReader->atEnd() && !departureReader->hasError()) {
        try {
            token = departureReader->readNext();
            if (token == QXmlStreamReader::StartElement) {
                attributes = departureReader->attributes();
                if (attributes.hasAttribute("queryTime"))
                    queryTime = attributes.value("", "queryTime").toString().toLongLong();
                if (attributes.hasAttribute("block")) {
                    QString destinationS = attributes.value("", "fullSign").toString();
                    bool found = false;
                    QStandardItem *iterDestination;
                    for (int i = 0; i < curRequestStop->rowCount() && !found; ++i) {
                        iterDestination = curRequestStop->child(i, COL_NAME);
                        if (iterDestination->text().compare(destinationS) == 0)
                            found = true;
                    }
                    if (found)
                        destination = iterDestination;
                    else {
                        destination = new QStandardItem(destinationS);
                        destination->setEditable(false);
                        curRequestStop->appendRow(destination);
                        view->expand(this->indexFromItem(destination));
                    }

                    QStandardItem *status = new QStandardItem(attributes.value("", "status").toString());
                    qlonglong departureTime = attributes.value("", status->text()).toString().toLongLong();
                    qlonglong delta;
                    if (departureTime < 10000)
                        delta = departureTime;
                    else
                        delta = (departureTime - queryTime) / 60000;
                    char departureTimeI[80];
                    if (delta <= 120) { // Skip departures beyond two hours
                        if (delta == 0)
                            sprintf(departureTimeI, "%s", "due");
                        else
                            sprintf(departureTimeI, "%d min", delta);
                        QString departureTimeS = departureTimeI;
                        QStandardItem *departure = new QStandardItem(departureTimeS);
                        if (status->text().compare("estimated") == 0)
                            foreground->setColor(QColor(250, 160, 10, 255)); // orange
                        else
                            foreground->setColor(QColor(10, 160, 250, 255));
                        departure->setForeground(*foreground);
                        destination->appendRow(departure);
                    }
                    delete status;
                }
            }
            QTimer::singleShot(0, this, SLOT(nextDeparture()));
        } catch (...) {
            displayMsg("Error reading departures");
            slotRequestStop = curRequestStop;
            departureNetworkReply->deleteLater();
            departuresNetworkXmlAccess->deleteLater();
            departureReader->clear();
            delete departureReader;
            departureReader = NULL;

            if (departureRequestTimer)
                delete departureRequestTimer;
            departureRequestTimer = new QTimer(this);
            connect(departureRequestTimer, SIGNAL(timeout()), this, SLOT(loadDepartures()));
            departureRequestTimer->setSingleShot(true);
            departureRequestTimer->start(30000);
            curRequestStop = NULL;
        }

    } else {
        QStandardItem *iterDestination;
        for (int i = 0; i < curRequestStop->rowCount(); ++i) {
            iterDestination = curRequestStop->child(i, COL_NAME);
            if (iterDestination->rowCount() == 0) {
                curRequestStop->removeRow(i);
                i -= 1;
            }
        }
        displayMsg("Done");

        if (curRequestStop->rowCount() == 0) {
            foreground->setColor(QColor(250, 160, 10, 255)); // orange
            QStandardItem *noDeparture = new QStandardItem("No predictions at this time");
            noDeparture->setForeground(*foreground);
            curRequestStop->appendRow(noDeparture);
        }

        view->scrollTo(this->indexFromItem(curRequestStop), QAbstractItemView::PositionAtTop);
        view->expand(this->indexFromItem(curRequestStop));
        slotRequestStop = curRequestStop;
        departureNetworkReply->deleteLater();
        departuresNetworkXmlAccess->deleteLater();
        departureReader->clear();
        delete departureReader;
        departureReader = NULL;

        if (departureRequestTimer)
            delete departureRequestTimer;
        departureRequestTimer = new QTimer(this);
        connect(departureRequestTimer, SIGNAL(timeout()), this, SLOT(loadDepartures()));
        departureRequestTimer->setSingleShot(true);
        departureRequestTimer->start(30000);
        departureQueryCtr += 1;
        curRequestStop = NULL;
    }
}

void TransiTreeModel::readDepartures(QNetworkReply *reply)
{
    displayMsg("Loading Departures");

    hourglass->hideBusyCursor();

    if (reply->error() != QNetworkReply::NoError){
        departureRequestTimer = new QTimer(this);
        connect(departureRequestTimer, SIGNAL(timeout()), this, SLOT(loadDepartures()));
        departureRequestTimer->setSingleShot(true);
        departureRequestTimer->start(6000);
        if (departureReader)
            departureReader->clear();
        reply->deleteLater();
        displayMsg("Server or Network Error");
        resetSession();
        curRequestStop = NULL;
        return;
    }

    departureNetworkReply = reply;
    departureReader = new QXmlStreamReader(reply);

    QTimer::singleShot(0, this, SLOT(nextDeparture()));
}

/*
 * Load Detours
 */
void TransiTreeModel::loadDetours(QStandardItem *stop)
{
    if (curRequestStopDetour && m_session->isOpen())
        return;

    if (!m_session->isOpen()) {
        resetSession();
        connect(m_session, SIGNAL(opened()), this, SLOT(loadDetours()));
        return;
    }

    curRequestStopDetour = stop;
    detourNetworkXmlAccess = new QNetworkAccessManager(this);
    connect(detourNetworkXmlAccess, SIGNAL(finished(QNetworkReply *)), this, SLOT(readDetours(QNetworkReply *)));
    requestDetours(stop);
    hourglass->showBusyCursor();
}

void TransiTreeModel::requestDetours(QStandardItem *stop)
{
    QStandardItem *route = stop->parent();
    QString stopId = route->child(stop->row(), COL_ID)->text();
    QString routeName = route->text();
    QString agencyName = route->parent()->text();
    displayMsg("Requesting Detours");
    std::string relayBuf;
    relayBuf = utilities->createAppKey();
    QString url = transitDataServer + servletPath + detourQuery + stopId + "&" + detourRouteQuery + routeName + "&" + departureAgencyQuery + agencyName+ "&" + appId + "&" + relayQuery + (QString)relayBuf.c_str() + "&" + versionQuery + parentVersion + personality;
    detourNetworkXmlAccess->get(QNetworkRequest(QUrl(url)));
}

void TransiTreeModel::nextDetour()
{
    QXmlStreamReader::TokenType token;
    QXmlStreamAttributes attributes;
    if (!detourReader->atEnd() && !detourReader->hasError()) {
        try {
            QString detourS;
            token = detourReader->readNext();
            if (token == QXmlStreamReader::StartElement) {
                attributes = detourReader->attributes();
                if (detourReader->name().compare("detour") == 0) {
                    detourS = attributes.value("", "desc").toString();
                }
                if (detourReader->name().compare("route") == 0) {
                    detourS = "Route: " + attributes.value("", "desc").toString() + "\n" + detourS;
                }
            }
            if (token == QXmlStreamReader::StartElement && detourReader->name().compare("detour") == 0) {
                messageBox->setInformativeText(messageBox->informativeText() + detourS + "\n\n");
                detourCount++;
            }
            QTimer::singleShot(0, this, SLOT(nextDetour()));
        } catch (...) {
            displayMsg("Error reading messages");
            detourNetworkReply->deleteLater();
            detourNetworkXmlAccess->deleteLater();
            detourReader->clear();
            delete detourReader;
            detourReader = NULL;
            curRequestStopDetour = NULL;
        }

    } else {
        if (detourCount == 0)
            messageBox->setInformativeText("No messages or detours reported");
        if (!messageBox->isVisible())
            messageBox->show();
        displayMsg("Done");
        detourNetworkReply->deleteLater();
        detourNetworkXmlAccess->deleteLater();
        detourReader->clear();
        delete detourReader;
        detourReader = NULL;
        curRequestStopDetour = NULL;
    }
}

void TransiTreeModel::readDetours(QNetworkReply *reply)
{
    displayMsg("Loading Detours");

    hourglass->hideBusyCursor();

    if (reply->error() != QNetworkReply::NoError){
        if (detourReader)
            detourReader->clear();
        reply->deleteLater();
        displayMsg("Server or Network Error");
        resetSession();
        curRequestStopDetour = NULL;
        return;
    }

    detourNetworkReply = reply;
    detourReader = new QXmlStreamReader(reply);

    QTimer::singleShot(0, this, SLOT(nextDetour()));
    detourCount = 0;

    messageBox = new QMessageBox(modelParent);
    messageBox->setText("\n" + slotRequestAgency->text() + " messages and detours for route " + slotRequestRoute->text() + " at " + slotRequestStop->text() + "\n");
    QPixmap appIconPixmap(":/appico.png");
    messageBox->setIconPixmap(appIconPixmap);
    messageBox->setAttribute(Qt::WA_TranslucentBackground, false);
    messageBox->addButton(QMessageBox::Ok);
}

void TransiTreeModel::contextSort()
{
    if (this->rowCount() == 0 || this->columnCount() == 0)
        return;

    bool sortByDist = false;

    QStandardItem *distancei = this->item(0, COL_DIST_KM);
    double distance = distancei->text().toDouble();
    if (distance != 99999)
        sortByDist = true;
    if (sortByDist)
        this->sort(COL_DIST_KM, Qt::AscendingOrder); // sort by agency name
    else
        this->sort(COL_NAME, Qt::AscendingOrder); // sort by distance to yougeoposinfo
}

void TransiTreeModel::startGpsSingleShot()
{
    startLocation(QGeoPositionInfoSource::SatellitePositioningMethods, true, NULL);
}
void TransiTreeModel::startNonGpsSingleShot()
{
    startLocation(QGeoPositionInfoSource::NonSatellitePositioningMethods, true, NULL);
}

void TransiTreeModel::startLocationListening(bool singleShot)
{
    if (singleShot)
        location->requestUpdate(1000);
    else {
        location->setUpdateInterval(2000);
        location->startUpdates();
        isUpdatingLocation = true;
        lastPosInfo = nullPosInfo;
    }
}

void TransiTreeModel::startLocation(enum QGeoPositionInfoSource::PositioningMethod method, bool singleShot, QStandardItem *route)
{
    displayMsg("Acquiring Location");

    trackingRoute = route;
    isSingleShot = singleShot;

    // QGeoPositionInfoSource: Start GPS and listen position changes
    if (!location) {
        location = QGeoPositionInfoSource::createDefaultSource(this);
        // System has some positioning source found
        if (location) {
            //m_location->setUpdateInterval(5*1000);
            connect(location, SIGNAL(positionUpdated(QGeoPositionInfo)), this, SLOT(positionUpdated(QGeoPositionInfo)));
            connect(location, SIGNAL(updateTimeout()), this, SLOT(updateTimeout()));
            // Get last known GPS position
            lastKnownPosRead = false;
            QGeoPositionInfo lastKnownPosition = location->lastKnownPosition();
            if (lastKnownPosition.isValid()) {
                lastKnownPosRead = true;
                positionUpdated(lastKnownPosition);
            }

            if (method)
                location->setPreferredPositioningMethods(method);

            // Start listening current GPS position
            startLocationListening(singleShot);

        } else {
            // System has not any positioning source
            // Could not found any positioning source. Close application.
            displayMsg("No positioning source found");
            return;
        }
    } else {

        if (method)
            location->setPreferredPositioningMethods(method);

        // Start listening current GPS position
        startLocationListening(singleShot);
    }
}

void TransiTreeModel::stopLocation() {
    displayMsg("");
    isUpdatingLocation = false;
    if (location == NULL)
        return;
    if (!isSingleShot)
        location->stopUpdates();
    trackingRoute = NULL;
    closestStopName = "";
}

bool TransiTreeModel::updatingLocation() {
    return isUpdatingLocation;
}

void TransiTreeModel::resetSession()
{
    m_session->close();
    m_session->disconnect();

    QNetworkConfigurationManager manager;
    const bool canStartIAP = (manager.capabilities() & QNetworkConfigurationManager::CanStartAndStopInterfaces);
    QNetworkConfiguration cfg = manager.defaultConfiguration();
    if (!cfg.isValid() || (!canStartIAP && cfg.state() != QNetworkConfiguration::Active)) {
        displayMsg("No available access points found");
        return;
    }
    m_session = new QNetworkSession(cfg, this);
    m_session->open();
}

void TransiTreeModel::updateTimeout()
{
    if (isSingleShot) {
        displayMsg("Position acquistion timed out");

        stopLocation();

        locationTimedOut = true;

        if (this->rowCount() == 0)
            QTimer::singleShot(0, this, SLOT(loadAgencies()));
    }
}

void TransiTreeModel::processPositionUpdate(QGeoPositionInfo gpsPos) {

    locationTimedOut = false;

    if (isSingleShot)
        stopLocation();

    if (this->rowCount() == 0)
        QTimer::singleShot(0, this, SLOT(loadAgencies()));
    else {
        if (gpsPos.isValid()) {
            QTimer::singleShot(0, this, SLOT(refreshDistances()));
            if (!isSingleShot) {
                if (gpsPos.hasAttribute(QGeoPositionInfo::GroundSpeed)) {
                    if (closestStopName.length() == 0)
                        displayMsg("Location found, awaiting travel");
                    if (gpsPos.attribute(QGeoPositionInfo::GroundSpeed) > 2) { // Only look for next stop if traveling at 2m/s or faster
                        if (lastPosInfo.isValid() && trackingRoute != NULL) { // Determine stop in approach, relative to last position
                            float distanceToStop;
                            float distanceToStopFromLastPosition;
                            float shortestDistanceToStop = 99999.9;
                            QString coordinate;
                            double lat;
                            double lon;
                            QStandardItem *closestStop = NULL;
                            for (int i = 0; i < trackingRoute->rowCount(); ++i) {
                                coordinate = trackingRoute->child(i, COL_LAT)->text();
                                lat = coordinate.toDouble();
                                coordinate = trackingRoute->child(i, COL_LON)->text();
                                lon = coordinate.toDouble();
                                distanceToStop = utilities->distance(gpsPos.coordinate().latitude(), gpsPos.coordinate().longitude(), lat, lon, 'K');
                                distanceToStopFromLastPosition = utilities->distance(lastPosInfo.coordinate().latitude(), lastPosInfo.coordinate().longitude(), lat, lon, 'K');
                                if (distanceToStop < shortestDistanceToStop && distanceToStop < distanceToStopFromLastPosition) {
                                    closestStop = trackingRoute->child(i, COL_NAME);
                                    closestStopName = closestStop->text();
                                    shortestDistanceToStop = distanceToStop;
                                }
                            }
                            if (closestStopName.length() > 0)
                                displayMsg("Approaching: " + closestStopName);
                            if (closestStop != NULL) {
                                QItemSelectionModel *selection = new QItemSelectionModel(this);
                                QModelIndex index = this->indexFromItem(closestStop);
                                selection->select(index, QItemSelectionModel::Select);
                                view->setSelectionModel(selection);
                                view->scrollTo(index, QAbstractItemView::PositionAtCenter);
                            }
                        }
                        lastPosInfo = gpsPos;
                    }
                }
            }
        }
    }
}

void TransiTreeModel::positionUpdated(QGeoPositionInfo gpsPos)
{
    // Take your GPS position only once and load agencies for that location
    if (gpsPos.isValid() && !yougeoPosInfo.isValid()) {
        // Get first position
        // GPS position got
        yougeoPosInfo = gpsPos;

        // Stop GPS, we have position
        if (!lastKnownPosRead)
            processPositionUpdate(gpsPos);
        else
            displayMsg("Waiting for current position");
    } else
        if (gpsPos.isValid() && yougeoPosInfo.isValid()) {
            yougeoPosInfo = gpsPos;
            processPositionUpdate(gpsPos);
        }
}

void TransiTreeModel::setItemDistance(int row)
{
    QString distance = this->calculateAgencyDistance(agencyTops.at(row), agencyLefts.at(row), agencyBottoms.at(row), agencyRights.at(row));
    this->setItem(row, COL_DIST_KM, new QStandardItem(distance));
}

QString TransiTreeModel::calculateAgencyDistance(QStandardItem *topi, QStandardItem *lefti, QStandardItem *bottomi, QStandardItem *righti)
{
    QString distance = "99999";

    if (yougeoPosInfo.isValid()) {
        double top = topi->text().toDouble();
        double left = lefti->text().toDouble();
        double bottom = bottomi->text().toDouble();
        double right = righti->text().toDouble();

        double distanceD = utilities->distance((top + bottom) / 2, (left + right) / 2,
            yougeoPosInfo.coordinate().latitude(), yougeoPosInfo.coordinate().longitude(), 'K');
        int distanceI = (int)distanceD;
        char distanceS[80];
        sprintf(distanceS, "%05d", distanceI);
        distance = distanceS;
    }
    return distance;
}

void TransiTreeModel::refreshDistances()
{
    int rowCount = this->rowCount();
    for (int i = 0; i < rowCount; ++i)
        this->setItemDistance(i);
    contextSort();
    this->dataChanged(this->indexFromItem(item(0, COL_DIST_KM)), this->indexFromItem(item(rowCount, COL_DIST_KM)));
}

void TransiTreeModel::manageSiblingVisibility(QStandardItem *item, bool selective)
{
    QStandardItem *parent;
    if (item->parent()) {
        if (selective)
            return;
        parent = item->parent();
    } else
        parent = this->invisibleRootItem();

    // Run through agencies
    QStandardItem *visibilityAgency;
    for (int i = 0; i < this->rowCount(); ++i) {
        visibilityAgency = this->item(i, 0);

        if (!selective)
            view->collapse(this->indexFromItem(visibilityAgency));

        // Collapse open routes
        for (int j = 0; j < visibilityAgency->rowCount(); ++j)
            view->collapse(this->indexFromItem(visibilityAgency->child(j, COL_NAME)));

        // Show/hide to expose all, or only the selected agency
        if (visibilityAgency != item && selective)
            view->setRowHidden(i, this->indexFromItem(parent), true);
        else
            view->setRowHidden(i, this->indexFromItem(parent), false);
    }
}

Bookmark *TransiTreeModel::createBookmark()
{
    // An agency must be expanded
    QStandardItem *parent = this->invisibleRootItem();
    QStandardItem *visibleAgency;
    int visibilityCount = 0;
    for (int i = 0; i < this->rowCount(); ++i) {
        if (!view->isRowHidden(i, this->indexFromItem(parent))) {
            visibilityCount++;
            visibleAgency = this->item(i, 0);
        }
    }
    if (visibilityCount != 1)
        return NULL;

    // Agency must have routes
    if (!visibleAgency->hasChildren())
        return NULL;

    Bookmark *result = new Bookmark();

    // Pick up selected agency
    int agencyRow = visibleAgency->row();

    result->setAgency(visibleAgency->text(), this->item(agencyRow, COL_ID)->text(),
        agencyTops.at(agencyRow)->text(), agencyLefts.at(agencyRow)->text(),
        agencyBottoms.at(agencyRow)->text(), agencyRights.at(agencyRow)->text(),
        agencyRts.at(agencyRow)->text());

    // Pick up expanded route
    QStandardItem *selectedRoute = NULL;
    for (int j = 0; j < visibleAgency->rowCount() && selectedRoute == NULL; ++j)
        if (view->isExpanded(this->indexFromItem(visibleAgency->child(j, COL_NAME))))
            selectedRoute = visibleAgency->child(j, COL_NAME);
    if (selectedRoute != NULL) {
        int routeRow = selectedRoute->row();
        result->setRoute((visibleAgency->child(routeRow, COL_NAME))->text());
    }

    // Pick up selected stop
    if (slotRequestStop != NULL) {
        int stopRow = slotRequestStop->row();
        result->setStop((slotRequestStop->parent()->child(stopRow, COL_NAME))->text(),
            (slotRequestStop->parent()->child(stopRow, COL_ID))->text());
    }

    return result;
}

void TransiTreeModel::setBookmark(Bookmark *bookmark)
{
    clearDepartures();
    clearDepartureRequestTimer();

    agencyItem = this->findItems(bookmark->getAgencyName(), Qt::MatchExactly, COL_NAME);
    if (agencyItem.size() == 0) {
        int rowCount = this->rowCount();
        bookmarkAgency = new QStandardItem(bookmark->getAgencyName());
        this->appendRow(bookmarkAgency);
        this->item(rowCount, COL_NAME)->setEditable(false);
        this->setItem(rowCount, COL_ID, new QStandardItem(bookmark->getAgencyId()));
        agencyTops.append(new QStandardItem(bookmark->getAgencyTop()));
        agencyLefts.append(new QStandardItem(bookmark->getAgencyLeft()));
        agencyBottoms.append(new QStandardItem(bookmark->getAgencyBottom()));
        agencyRights.append(new QStandardItem(bookmark->getAgencyRight()));
        this->setItemDistance(rowCount);
        agencyRts.append(new QStandardItem(bookmark->getAgencyRt()));

        // Hide support columns
        if (rowCount == 0)
            for (int i = COL_NAME + 1; i <= COL_DIST_KM; ++i)
                view->setColumnHidden(i, true);

        contextSort();
    } else
        bookmarkAgency = agencyItem.at(0);
    manageSiblingVisibility(bookmarkAgency, true);

    bookmarkFollowupRoute = bookmark->getRouteName();
    bookmarkFollowupStop = bookmark->getStopName();

    if (bookmarkAgency->rowCount() > 0) {
        view->collapse(this->indexFromItem(bookmarkAgency));
        bookmarkAgency->removeRows(0, bookmarkAgency->rowCount());
    }
    curRequestAgency = bookmarkAgency;
    QTimer::singleShot(0, this, SLOT(loadRoutes()));

 /*   if (bookmarkAgency->rowCount() == 0) { // better optimized but makes the Qt runtime freak out for unknown reason; hangup or crash in event loop
        curRequestAgency = bookmarkAgency;
        QTimer::singleShot(0, this, SLOT(loadRoutes()));
    }  else {
        view->expand(this->indexFromItem(bookmarkAgency));
        if (bookmarkFollowupRoute.length() > 0)
            QTimer::singleShot(0, this, SLOT(bookmarkFollowup()));
    } */
}

void TransiTreeModel::bookmarkFollowup()
{
    bool found = false;
    QStandardItem *iterRoute;

    for (int i = 0; i < bookmarkAgency->rowCount() && !found; ++i) {
        iterRoute = bookmarkAgency->child(i, COL_NAME);
        if (iterRoute->text().compare(bookmarkFollowupRoute) == 0)
            found = true;
    }
    bookmarkAgency = NULL;
    bookmarkFollowupRoute = "";

    if (!found) {
        MessageBox box;
        box.messageBox(modelParent, "\nTransiCast\n\nBookmark route not found", QMessageBox::Information, NULL);
        bookmarkFollowupStop = "";
        return;
    }

    if (iterRoute->rowCount() == 0) {
        curRequestRoute = iterRoute;
        QTimer::singleShot(0, this, SLOT(loadRoute()));
    } else {
        view->expand(this->indexFromItem(iterRoute));
        view->scrollTo(this->indexFromItem(iterRoute), QAbstractItemView::PositionAtTop);
        bookmarkFollowupDepartureRoute = iterRoute;
        if (bookmarkFollowupStop.length() > 0)
            QTimer::singleShot(0, this, SLOT(bookmarkFollowupDeparture()));
    }
}

void TransiTreeModel::bookmarkFollowupDeparture()
{
    bool found = false;
    QStandardItem *iterStop;
    for (int i = 0; i < bookmarkFollowupDepartureRoute->rowCount() && !found; ++i) {
        iterStop = bookmarkFollowupDepartureRoute->child(i, COL_NAME);
        if (iterStop->text().compare(bookmarkFollowupStop) == 0)
            found = true;
    }
    bookmarkFollowupDepartureRoute = NULL;
    bookmarkFollowupStop = "";

    if (!found) {
        MessageBox box;
        box.messageBox(modelParent, "\nTransiCast\n\nBookmark stop not found", QMessageBox::Information, NULL);
        return;
    }

    slotClicked = false;
    slotRequestStop = iterStop;
    departureQueryCtr = 0;
    QTimer::singleShot(0, this, SLOT(loadDepartures()));
}
